package com.xiaobias.a27;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.*;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.util.UrlPathHelper;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;

public class A27 {

    private static final Logger logger = LoggerFactory.getLogger(A27.class);

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        // 1. 测试还回值类型为 ModelAndView
        //test1(context);

        // 2. 测试还回值类型为 String 时， 把它当作视图名
//        test2(context);

        // 3. 测试还回值添加了 @ModelAttribute 注解时，此时需要找到默认视图名
//        test3(context);

        // 4. 测试还回值不加 @ModelAttribute 注解且返回简单类型时，此时需要找到默认视图名
//        test4(context);

        // 5. 测试返回类型为 ResponseEntity 时，此时不走视图流程
        test5(context);

        // 6. 测试返回类型为 HttpHeaders 时，此时不走视图流程
//        test6(context);

        // 7. 测试返回值添加了 @ResponseBody 时，此时不走视图流程
//        test7(context);

    }

    private static void test7(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test7");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                // 渲染视图
                renderView(context, container, webRequest);
            } else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test6(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test6");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                // 渲染视图
                renderView(context, container, webRequest);
            } else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test5(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test5");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                // 渲染视图
                renderView(context, container, webRequest);
            } else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }
    private static void test4(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test4");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());

            // 渲染视图
            renderView(context, container, webRequest);
        }
    }

    private static void test3(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test3");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test3");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());

            // 渲染视图
            renderView(context, container, webRequest);
        }
    }

    private static void test2(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test2");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());

            // 渲染视图
            renderView(context, container, webRequest);
        }
    }

    private static void test1(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getDeclaredMethod("test1");
        Controller controller = new Controller();
        // 获取返回值
        Object returnValue = method.invoke(controller);

        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        ModelAndViewContainer container = new ModelAndViewContainer();

        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        // 检查是否支持此类型的返回值
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());

            // 渲染视图
            renderView(context, container, webRequest);
        }
    }

    public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }

    public static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
                                  ServletWebRequest webRequest) throws Exception {
        logger.debug(">>>>>> 渲染视图");
        FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
        String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
        logger.debug("没有获取到视图名, 采用默认视图名: {}", viewName);
        // 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
        View view = resolver.resolveViewName(viewName, Locale.getDefault());
        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
        System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    static class Controller {
        private static final Logger logger = LoggerFactory.getLogger(Controller.class);

        public ModelAndView test1() {
            logger.debug("test1()");
            ModelAndView mav = new ModelAndView("view1");
            mav.addObject("name", "张三");
            return mav;
        }

        public String test2() {
            logger.debug("test2()");
            return "view2";
        }

        @ModelAttribute(name = "user")
        public User test3() {
            logger.debug("test3()");
            return new User("李四", 20);
        }

        public User test4() {
            logger.debug("test4()");
            return new User("王五", 30);
        }

        public HttpEntity<User> test5() {
            logger.debug("test5");
            return new HttpEntity<>(new User("赵六", 40));
        }

        public HttpHeaders test6() {
            logger.debug("test6()");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "text/html");
            return headers;
        }

        @ResponseBody
        public User test7() {
            logger.debug("test7");
            return new User("钱七", 50);
        }
    }

    public static class User {
        private String name;
        private Integer age;

        public User() {
        }

        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}
